#include "serializationengine.h"

#include "iserializable.h"

#include <QtCore/QHash>
#include <QtCore/QPointF>
#include <QtCore/QStringList>
#include <QtGui/QColor>

using namespace core;

QString SerializationEngine::serialize(const ISerializable* obj,
    bool* serializedProperly) const
{
    QString ret = "{";

    const QHash<QString, QVariantList> propValueMapping =
        obj->serializationData(*this);

    bool proper = true;

    foreach (const QString& property, propValueMapping.keys())
    {
        QVariant::Type type = QVariant::Invalid;

        const QVariantList values = propValueMapping.value(property);

        foreach (const QVariant& value, values)
        {
            if (type == QVariant::Invalid)
            {
                type = value.type();
            }

            if (!value.isValid() || type != value.type())
            {
                proper = false;
                break;
            }
        }

        if (!proper)
        {
            break;
        }
        else
        {
            QString serProp;

            switch (type)
            {
                case QVariant::String:
                    serProp = serializeString(property,
                        values, proper);

                    break;
                case QVariant::Int:
                    serProp = serializeInt(property,
                        values, proper);

                    break;
                case QVariant::UInt:
                    serProp = serializeUInt(property,
                        values, proper);

                    break;
                case QVariant::Double:
                    serProp = serializeReal(property,
                        values, proper);

                    break;
                case QVariant::PointF:
                    if (values.size() > 1)
                    {
                        serProp = serializeListOfPoints(property,
                            values, proper);
                    }
                    else
                    {
                        serProp = serializePoint(property,
                            values, proper);
                    }

                    break;
                case QVariant::Color:
                    serProp = serializeColor(property,
                        values, proper);

                    break;
                default:
                    proper = false;
                    break;
            }

            if (proper)
            {
                ret += QString("%1;").arg(serProp);
            }
        }
    }

    if (serializedProperly != 0)
    {
        *serializedProperly = proper;
    }

    ret = ret.length() > 1 ? ret.left(ret.length() - 1) : ret;
    ret += "}";

    return ret;
}

bool SerializationEngine::deserialize(const QString& serialized,
    ISerializable* obj) const
{
    bool ret = true;

    if (serialized.contains(QRegExp("\\{(.*;{0,1})+\\}")))
    {
        QString ser = serialized.left(serialized.length() - 1);
        ser = ser.right(ser.length() - 1);

        const QStringList properties = split(ser, ';', '{', '}');

        if (properties.size() > 0)
        {
            foreach (const QString& property, properties)
            {
                QStringList propertyNameTypeValue = split(property, '=', '{', '}');

                if (propertyNameTypeValue.size() == 2)
                {
                    const QString typeTemp = propertyNameTypeValue.at(1);

                    propertyNameTypeValue = propertyNameTypeValue.at(0).split('#',
                        QString::SkipEmptyParts);
                    propertyNameTypeValue.append(typeTemp);

                    if (propertyNameTypeValue.size() == 3)
                    {
                        bool properTemp = true;

                        const QVariant::Type type = static_cast<QVariant::Type>(
                            propertyNameTypeValue.at(1).toInt(&properTemp));

                        if (properTemp && type != QVariant::Invalid)
                        {
                            QVariantList value;
                            const QString serValue = propertyNameTypeValue.at(2);

                            switch (type)
                            {
                                case QVariant::String:
                                    value = deserializeString(serValue,
                                        properTemp);

                                    break;
                                case QVariant::Int:
                                    value = deserializeInt(serValue,
                                        properTemp);

                                    break;
                                case QVariant::UInt:
                                    value = deserializeUInt(serValue,
                                        properTemp);

                                    break;
                                case QVariant::Double:
                                    value = deserializeReal(serValue,
                                        properTemp);

                                    break;
                                case QVariant::PointF:
                                    if (!serValue.contains('[') &&
                                        !serValue.contains(']'))
                                    {
                                        value = deserializePoint(serValue,
                                            properTemp);
                                    }
                                    else
                                    {
                                        value = deserializeListOfPoints(serValue,
                                            properTemp);
                                    }

                                    break;
                                case QVariant::Color:
                                    value = deserializeColor(serValue, properTemp);

                                    break;
                                default:
                                    properTemp = false;
                                    break;
                            }

                            ret = ret && properTemp;

                            if (ret)
                            {
                                obj->doActionForProperty(propertyNameTypeValue.at(0),
                                    value, *this);
                            }
                            else
                            {
                                break;
                            }
                        }
                        else
                        {
                            ret = false;
                            break;
                        }
                    }
                    else
                    {
                        ret = false;
                        break;
                    }
                }
                else
                {
                    ret = false;
                    break;
                }
            }
        }
    }
    else
    {
        ret = false;
    }

    obj->deserializationFinished(ret);

    return ret;
}

QString SerializationEngine::serializeString(
    const QString& property, const QVariantList& string,
    bool& proper) const
{
    QString ret;

    const QVariant& value = string.first();

    const QString _value = value.toString();

    proper = value.canConvert(QVariant::String);

    ret = QString("%1#%2=\"%3\"").arg(property).arg(
        QVariant::String).arg(_value);

    return ret;
}

QString SerializationEngine::serializeInt(
    const QString& property, const QVariantList& integer,
    bool& proper) const
{
    QString ret;

    const QVariant& value = integer.first();

    const int _value = value.toInt();

    proper = value.canConvert(QVariant::Int);

    ret = QString("%1#%2=%3").arg(property).arg(
        QVariant::Int).arg(_value);

    return ret;
}

QString SerializationEngine::serializeUInt(
    const QString& property, const QVariantList& integer,
    bool& proper) const
{
    QString ret;

    const QVariant& value = integer.first();

    const uint _value = value.toUInt();

    proper = value.canConvert(QVariant::UInt);

    ret = QString("%1#%2=%3").arg(property).arg(
        QVariant::UInt).arg(_value);

    return ret;
}

QString SerializationEngine::serializeReal(
    const QString& property, const QVariantList& real,
    bool& proper) const
{
    QString ret;

    const QVariant& value = real.first();

    const qreal _value = value.toReal();

    proper = value.canConvert(QVariant::Double);

    ret = QString("%1#%2=%3").arg(property).arg(
        QVariant::Double).arg(_value);

    return ret;
}

QString SerializationEngine::serializePoint(
    const QString& property, const QVariantList& point,
    bool& proper) const
{
    QString ret;

    const QVariant& value = point.first();

    const QPointF _value = value.toPointF();

    proper = value.canConvert(QVariant::PointF);

    ret = QString("%1#%2=(%3,%4)").arg(property).arg(
        QVariant::PointF).arg(_value.x()).arg(_value.y());

    return ret;
}

QString SerializationEngine::serializeListOfPoints(
    const QString& property, const QVariantList& points,
    bool& proper) const
{
    QString ret;

    QString _value;

    foreach (const QVariant& value, points)
    {
        const QPointF point = value.toPointF();

        proper = proper && value.canConvert(QVariant::PointF);

        _value += QString("(%1,%2),").arg(point.x()).arg(point.y());
    }

    _value = _value.isEmpty() ? _value : _value.left(_value.length() -1 );

    ret = QString("%1#%2=[%3]").arg(property).arg(
        QVariant::PointF).arg(_value);

    return ret;
}

QString SerializationEngine::serializeColor(const QString& property,
    const QVariantList& color, bool& proper) const
{
    QString ret;

    const QVariant& value = color.first();

    const QColor _value = value.value<QColor>();

    proper = value.canConvert(QVariant::Color);

    const uint r = _value.red() << 24;
    const uint g = _value.green() << 16;
    const uint b = _value.blue() << 8;
    const uint a = _value.alpha();

    const uint hex = r | g | b | a;

    const QString _color = QString("%1").arg(
        hex, 8, 16, QChar('0')).toUpper();

    ret = QString("%1#%2=0x%3").arg(property).arg(
        QVariant::Color).arg(_color);

    return ret;
}

QVariantList SerializationEngine::deserializeString(const QString& serialized,
    bool& proper) const
{
    QVariantList ret;

    QString stripped = "";

    if (serialized.length() >= 2)
    {
        stripped = serialized.left(serialized.length() - 1);
        stripped = stripped.right(stripped.length() - 1);

        proper = true;
    }
    else
    {
        proper = false;
    }

    ret.append(QVariant(stripped));

    return ret;
}

QVariantList SerializationEngine::deserializeInt(const QString& serialized,
    bool& proper) const
{
    QVariantList ret;

    ret.append(QVariant(serialized.toInt(&proper)));

    return ret;
}

QVariantList SerializationEngine::deserializeUInt(const QString& serialized,
    bool& proper) const
{
    QVariantList ret;

    ret.append(QVariant(serialized.toUInt(&proper)));

    return ret;
}

QVariantList SerializationEngine::deserializeReal(const QString& serialized,
    bool& proper) const
{
    QVariantList ret;

    ret.append(QVariant(serialized.toDouble(&proper)));

    return ret;
}

QVariantList SerializationEngine::deserializePoint(const QString& serialized,
    bool& proper) const
{
    QVariantList ret;

    ret.append(QVariant(deserializePointHelper(serialized, proper)));

    return ret;
}

QVariantList SerializationEngine::deserializeListOfPoints(
    const QString& serialized, bool& proper) const
{
    QVariantList ret;

    proper = true;

    if (serialized.contains(QRegExp("\\[(\\(.*\\)\\,{0,1})*\\]")))
    {
        QString ser = serialized.left(serialized.length() - 1);
        ser = ser.right(ser.length() - 1);

        const QStringList points = split(ser, ',', '(', ')');

        foreach (const QString& point, points)
        {
            bool properTemp  = true;
            QPointF _point = deserializePointHelper(point, properTemp);

            proper = proper && properTemp;

            if (proper)
            {
                ret.append(QVariant(_point));
            }
            else
            {
                break;
            }
        }
    }
    else
    {
        proper = false;
    }

    return ret;
}

QVariantList SerializationEngine::deserializeColor(const QString& serialized,
    bool& proper) const
{
    QVariantList ret;

    proper = true;

    if (serialized.startsWith("0x"))
    {        
        const QString color = serialized.right(serialized.length() - 2);

        const uint hex = color.toUInt(&proper, 16);

        uint mask = 0xFF << 24;
        const int r = (hex & mask) >> 24;
        mask = 0xFF << 16;
        const int g = (hex & mask) >> 16;
        mask = 0xFF << 8;
        const int b = (hex & mask) >> 8;
        mask = 0xFF;
        const int a = hex & mask;

        if (proper)
        {
            ret.append(QVariant(QColor(r, g, b, a)));
        }
    }
    else
    {
        proper = false;
    }

    return ret;
}

QPointF SerializationEngine::deserializePointHelper(const QString &serialized,
    bool &proper) const
{
    QPointF ret;

    const QStringList list = serialized.split(',', QString::SkipEmptyParts);

    if (list.size() == 2 && list.at(0).contains('(') &&
        list.at(1).contains(')'))
    {
        QString x = list.at(0);
        x = x.right(x.length() - 1);

        QString y = list.at(1);
        y = y.left(y.length() - 1);

        bool properTemp = true;

        ret = QPointF(x.toDouble(&proper), y.toDouble(&properTemp));

        proper = proper && properTemp;
    }
    else
    {
        proper = false;
    }

    return ret;
}

QStringList SerializationEngine::split(const QString& serialized,
    const QChar& separator, const QChar& leftLeveler, const QChar& rightLeveler) const
{
    QStringList ret;

    int lastIndex = 0;
    int level = 0;

    for (int i = 0; i < serialized.length(); ++i)
    {
        if (serialized[i] == separator && level == 0)
        {
            const QString part = serialized.mid(
                lastIndex, i - lastIndex);
            ret.append(part);

            lastIndex = i + 1;
        }
        else if (serialized[i] == leftLeveler)
        {
            ++level;
        }
        else if (serialized[i] == rightLeveler)
        {
            --level;
        }
    }

    if (lastIndex <= (serialized.length() - 1))
    {
        const QString part = serialized.right(
            serialized.length() - lastIndex);
        if (!part.isEmpty())
        {
            ret.append(part);
        }
    }

    return ret;
}

